/**
 * Copyright (C) 2021-2022 maminjie <canpool@163.com>
 * SPDX-License-Identifier: LGPL-2.1
 * Powered by Qt-Advanced-Docking-System
**/

#include "dockfocuscontroller.h"
#include "dockpannel.h"
#include "docktitlebar.h"
#include "dockcontainer.h"
#include "dockmanager.h"
#include "dockmanager_p.h"
#include "dockwidget.h"
#include "docktab.h"
#include "dockfloatingwidget.h"

#ifdef Q_OS_LINUX
#include "linux/dockfloatingwidgettitlebar.h"
#endif

#include <QApplication>
#include <QPointer>
#include <QWindow>

QADS_BEGIN_NAMESPACE

static const char* const FocusedDockWidgetProperty = "FocusedDockWidget";

static void updateDockWidgetFocusStyle(DockWidget *dockWidget, bool focused)
{
    dockWidget->setProperty("focused", focused);
    dockWidget->tab()->setProperty("focused", focused);
    dockWidget->tab()->updateStyle();
    internal::repolishStyle(dockWidget);
}

static void updateDockPannelFocusStyle(DockPannel *pannel, bool focused)
{
    pannel->setProperty("focused", focused);
    internal::repolishStyle(pannel);
    internal::repolishStyle(pannel->titleBar());
}

#ifdef Q_OS_LINUX
static void updateFloatingWidgetFocusStyle(DockFloatingWidget *floatingWidget, bool focused)
{
    if (floatingWidget->hasNativeTitleBar()) {
        return;
    }

    auto titleBar = qobject_cast<DockFloatingWidgetTitleBar *>(floatingWidget->titleBarWidget());
    if (!titleBar) {
        return;
    }

    titleBar->setProperty("focused", focused);
    titleBar->updateStyle();
}
#endif

/* DockFocusControllerPrivate */
class DockFocusControllerPrivate
{
public:
    DockFocusControllerPrivate(DockFocusController *parent);

    void updateDockWidgetFocus(DockWidget *dockWidget);

public:
    DockFocusController *q;
    QPointer<DockWidget> m_focusedDockWidget = nullptr;
    QPointer<DockPannel> m_focusedPannel = nullptr;
    DockWidget* m_oldFocusedDockWidget = nullptr;
#ifdef Q_OS_LINUX
    QPointer<DockFloatingWidget> m_floatingWidget = nullptr;
#endif
    DockManager *m_dockManager = nullptr;
    bool m_forceFocusChangedSignal = false;
}; // class DockFocusControllerPrivate

DockFocusControllerPrivate::DockFocusControllerPrivate(DockFocusController *parent)
    : q(parent)
{}

/**
 * This function updates the focus style of the given dock widget and
 * the dock area that it belongs to
 */
void DockFocusControllerPrivate::updateDockWidgetFocus(DockWidget *dockWidget)
{
    if (!dockWidget->features().testFlag(DockWidget::DockWidgetFocusable)) {
        return;
    }

    QWindow *window = nullptr;
    auto dockContainer = dockWidget->dockContainer();
    if (dockContainer) {
        window = dockContainer->window()->windowHandle();
    }
    if (window) {
        window->setProperty(FocusedDockWidgetProperty, QVariant::fromValue<DockWidget*>(dockWidget));
    }

    DockPannel *newFocusedPannel = nullptr;

    if (m_focusedDockWidget) {
        updateDockWidgetFocusStyle(m_focusedDockWidget, false);
    }

    DockWidget *old = m_focusedDockWidget;
    m_focusedDockWidget = dockWidget;
    updateDockWidgetFocusStyle(m_focusedDockWidget, true);
    newFocusedPannel = m_focusedDockWidget->dockPannel();

    if (newFocusedPannel && (m_focusedPannel != newFocusedPannel)) {
        if (m_focusedPannel) {
            QObject::disconnect(m_focusedPannel, &DockPannel::viewToggled,
                                q, &DockFocusController::onFocusedDockPannelViewToggled);
            updateDockPannelFocusStyle(m_focusedPannel, false);
        }
        m_focusedPannel = newFocusedPannel;
        updateDockPannelFocusStyle(m_focusedPannel, true);
        QObject::connect(m_focusedPannel, &DockPannel::viewToggled,
                         q, &DockFocusController::onFocusedDockPannelViewToggled);
    }

    DockFloatingWidget *newFloatingWidget = nullptr;
    dockContainer = m_focusedDockWidget->dockContainer();
    if (dockContainer) {
        newFloatingWidget = dockContainer->floatingWidget();
    }
    if (newFloatingWidget) {
        newFloatingWidget->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(dockWidget));
    }

#ifdef Q_OS_LINUX
    // This code is required for styling the floating widget titlebar for linux
    // depending on the current focus state
    if (m_floatingWidget != newFloatingWidget) {
        if (m_floatingWidget) {
            updateFloatingWidgetFocusStyle(m_floatingWidget, false);
        }
        m_floatingWidget = newFloatingWidget;
        if (m_floatingWidget) {
            updateFloatingWidgetFocusStyle(m_floatingWidget, true);
        }
    }
#endif

    if (old == dockWidget && !m_forceFocusChangedSignal) {
        return;
    }

    m_forceFocusChangedSignal = false;
    if (dockWidget->isVisible()) {
        emit m_dockManager->focusedDockWidgetChanged(old, dockWidget);
    } else {
        m_oldFocusedDockWidget = old;
        QObject::connect(dockWidget, SIGNAL(visibilityChanged(bool)), q, SLOT(onDockWidgetVisibilityChanged(bool)));
    }
}

DockFocusController::DockFocusController(DockManager *dockManager)
    : QObject(dockManager)
    , d(new DockFocusControllerPrivate(this))
{
    d->m_dockManager = dockManager;
    connect(qApp, &QApplication::focusChanged, this, &DockFocusController::onApplicationFocusChanged);
    connect(QApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)), this, SLOT(onFocusWindowChanged(QWindow*)));
    connect(d->m_dockManager, &DockManager::stateRestored, this, &DockFocusController::onStateRestored);
}

DockFocusController::~DockFocusController()
{
    delete d;
}

void DockFocusController::setWidgetFocus(QWidget *widget)
{
    if (!QAds::testConfigFlag(QAds::FocusHighlighting)) {
        return;
    }
    widget->setFocus(Qt::OtherFocusReason);
}

/**
 * Request focus highlighting for the given dock widget assigned to the tab
 * given in Tab parameter
 */
void DockFocusController::setDockTabFocused(DockTab *tab)
{
    auto dockWidget = tab->dockWidget();
    if (dockWidget) {
        d->updateDockWidgetFocus(dockWidget);
    }
}

/**
 * Request a focus change to the given dock widget
 */
void DockFocusController::setDockWidgetFocused(DockWidget *focusedNow)
{
    d->updateDockWidgetFocus(focusedNow);
}

/**
 * Returns the dock widget that has focus style in the ui or a nullptr if
 * not dock widget is painted focused.
 */
DockWidget *DockFocusController::focusedDockWidget() const
{
    return d->m_focusedDockWidget.data();
}

/**
 * A container needs to call this function if a widget has been dropped into it
 */
void DockFocusController::notifyWidgetOrPannelRelocation(QWidget *droppedWidget)
{
    if (d->m_dockManager->isRestoringState()) {
        return;
    }

    DockWidget *dockWidget = qobject_cast<DockWidget *>(droppedWidget);
    if (!dockWidget) {
        DockPannel *pannel = qobject_cast<DockPannel *>(droppedWidget);
        if (pannel) {
            dockWidget = pannel->currentDockWidget();
        }
    }

    if (!dockWidget) {
        return;
    }

    d->m_forceFocusChangedSignal = true;
    setWidgetFocus(dockWidget);
}

/**
 * This function is called, if a floating widget has been dropped into an new position.
 * When this function is called, all dock widgets of the FloatingWidget are already inserted into its new position
 */
void DockFocusController::notifyFloatingWidgetDrop(DockFloatingWidget *floatingWidget)
{
    if (!floatingWidget || d->m_dockManager->isRestoringState()) {
        return;
    }

    auto vDockWidget = floatingWidget->property(FocusedDockWidgetProperty);
    if (!vDockWidget.isValid()) {
        return;
    }

    auto dockWidget = vDockWidget.value<QPointer<DockWidget> >();
    if (dockWidget) {
        dockWidget->dockPannel()->setCurrentDockWidget(dockWidget);
        setWidgetFocus(dockWidget);
    }
}

void DockFocusController::onApplicationFocusChanged(QWidget *focusedOld, QWidget *focusedNow)
{
    Q_UNUSED(focusedOld);

    if (d->m_dockManager->isRestoringState()) {
        return;
    }
    if (!focusedNow) {
        return;
    }

    DockWidget *dockWidget = qobject_cast<DockWidget *>(focusedNow);
    if (!dockWidget) {
        dockWidget = internal::findParent<DockWidget *>(focusedNow);
    }

#ifdef Q_OS_LINUX
    if (!dockWidget) {
        return;
    }
#else
    if (!dockWidget || dockWidget->tab()->isHidden()) {
        return;
    }
#endif
    d->updateDockWidgetFocus(dockWidget);
}

void DockFocusController::onFocusWindowChanged(QWindow *focusWindow)
{
    if (!focusWindow) {
        return;
    }
    auto vDockWidget = focusWindow->property(FocusedDockWidgetProperty);
    if (!vDockWidget.isValid()) {
        return;
    }
    auto dockWidget = vDockWidget.value<QPointer<DockWidget> >();
    if (!dockWidget) {
        return;
    }
    d->updateDockWidgetFocus(dockWidget);
}

void DockFocusController::onFocusedDockPannelViewToggled(bool open)
{
    if (d->m_dockManager->isRestoringState()) {
        return;
    }

    DockPannel *pannel = qobject_cast<DockPannel *>(sender());
    if (!pannel || open) {
        return;
    }

    auto container = pannel->dockContainer();
    auto openedDockPannels = container->openedDockPannels();
    if (openedDockPannels.isEmpty()) {
        return;
    }

    d->updateDockWidgetFocus(openedDockPannels[0]->currentDockWidget());
}

void DockFocusController::onStateRestored()
{
    if (d->m_focusedDockWidget) {
        updateDockWidgetFocusStyle(d->m_focusedDockWidget, false);
    }
}

void DockFocusController::onDockWidgetVisibilityChanged(bool Visible)
{
    auto sender = this->sender();
    auto dockWidget = qobject_cast<QAds::DockWidget*>(sender);
    disconnect(sender, SIGNAL(visibilityChanged(bool)), this, SLOT(onDockWidgetVisibilityChanged(bool)));
    if (dockWidget && Visible) {
        emit d->m_dockManager->focusedDockWidgetChanged(d->m_oldFocusedDockWidget, dockWidget);
    }
}

QADS_END_NAMESPACE

